package com.enmonster.result;

import java.lang.reflect.*;
import java.util.Arrays;

/**
 * @Author wuxi
 * @create 2019/4/9 下午8:14
 *
 * org.apache.ibatis.reflection.TypeParameterResolver
 */
public class ResultType {
    //返回类型解析器
    public static Type resolveReturnType(Method method) {

        //获取方法的返回类型
        Type returnType = method.getGenericReturnType();

        //获取方法定义的类的类型
        Class<?> declaringClass = method.getDeclaringClass();

        //实际解析
        return resolveType(returnType, null, declaringClass);
    }

    //方法参数类型解析器
    public static Type[] resolveParamTypes(Method method, Type srcType) {

        //获取方法所有参数类型
        Type[] paramTypes = method.getGenericParameterTypes();

        //获取方法定义的类类型
        Class<?> declaringClass = method.getDeclaringClass();
        Type[] result = new Type[paramTypes.length];
        for (int i = 0; i < paramTypes.length; i++) {
            //对每个参数的类型进行解析
            result[i] = resolveType(paramTypes[i], srcType, declaringClass);
        }
        return result;
    }

    /**类型解析器
     *
     * @param type
     * @param srcType
     * @param declaringClass
     * @return
     */
    private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
        if (type instanceof TypeVariable) {
            //解析TypeVariable类型 : 表示类型变量。比如T, K等用来代表指定类型的类型变量
            return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
        } else if (type instanceof ParameterizedType) {
            //解析ParameterizedType类型 : , 比如Map<K, V>, List<V>
            return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
        } else if (type instanceof GenericArrayType) {
            //解析GenericArrayType类型 : 参数化类型的数组，即泛型数组，比如T[]
//            return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
        } else {
            //如果为普通的Class类型就直接返回
            return type;
        }
        return type;
    }


    private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type srcType, Class<?> declaringClass) {
        Class<?> rawType = (Class)parameterizedType.getRawType();
        Type[] typeArgs = parameterizedType.getActualTypeArguments();
        Type[] args = new Type[typeArgs.length];

        for(int i = 0; i < typeArgs.length; ++i) {
            if (typeArgs[i] instanceof TypeVariable) {
                args[i] = resolveTypeVar((TypeVariable)typeArgs[i], srcType, declaringClass);
            } else if (typeArgs[i] instanceof ParameterizedType) {
                args[i] = resolveParameterizedType((ParameterizedType)typeArgs[i], srcType, declaringClass);
            } else {
                args[i] = typeArgs[i];
            }
        }

        return new ResultType.ParameterizedTypeImpl(rawType, (Type)null, args);
    }

    private static Type resolveTypeVar(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass) {
        Type result = null;
        Class<?> clazz = null;
        if (srcType instanceof Class) {
            clazz = (Class)srcType;
        } else {
            if (!(srcType instanceof ParameterizedType)) {
                throw new IllegalArgumentException("The 2nd arg must be Class or ParameterizedType, but was: " + srcType.getClass());
            }

            ParameterizedType parameterizedType = (ParameterizedType)srcType;
            clazz = (Class)parameterizedType.getRawType();
        }

        if (clazz == declaringClass) {
            Type[] bounds = typeVar.getBounds();
            return (Type)(bounds.length > 0 ? bounds[0] : Object.class);
        } else {

            Type superclass = clazz.getGenericSuperclass();
            return superclass;
        }
    }

    static class ParameterizedTypeImpl implements ParameterizedType {
        private Class<?> rawType;
        private Type ownerType;
        private Type[] actualTypeArguments;

        public ParameterizedTypeImpl(Class<?> rawType, Type ownerType, Type[] actualTypeArguments) {
            this.rawType = rawType;
            this.ownerType = ownerType;
            this.actualTypeArguments = actualTypeArguments;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return this.actualTypeArguments;
        }

        @Override
        public Type getOwnerType() {
            return this.ownerType;
        }

        @Override
        public Type getRawType() {
            return this.rawType;
        }

        @Override
        public String toString() {
            return "ParameterizedTypeImpl [rawType=" + this.rawType + ", ownerType=" + this.ownerType + ", actualTypeArguments=" + Arrays.toString(this.actualTypeArguments) + "]";
        }
    }



}
